package com.wisdom.collect.modbus.io;


import com.wisdom.collect.modbus.Modbus;
import com.wisdom.collect.modbus.ModbusException;
import com.wisdom.collect.modbus.ModbusIOException;
import com.wisdom.collect.modbus.ModbusSlaveException;
import com.wisdom.collect.modbus.msg.ExceptionResponse;
import com.wisdom.collect.modbus.msg.ModbusRequest;
import com.wisdom.collect.modbus.net.AbstractSerialConnection;
import com.wisdom.collect.modbus.util.ModbusUtil;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ModbusSerialTransaction extends ModbusTransaction {

    private static final Logger logger = LoggerFactory.getLogger(ModbusSerialTransaction.class);

    //instance attributes and associations
    private int transDelayMS = Modbus.DEFAULT_TRANSMIT_DELAY;
    private final Object MUTEX = new Object();
    private long lastTransactionTimestamp = 0;

    public ModbusSerialTransaction() {
    }

    public ModbusSerialTransaction(ModbusRequest request) {
        setRequest(request);
    }

    public ModbusSerialTransaction(AbstractSerialConnection con) {
        setSerialConnection(con);
    }


    public void setSerialConnection(AbstractSerialConnection con) {
        synchronized (MUTEX) {
            transport = con.getModbusTransport();
        }
    }

    public void setTransport(ModbusSerialTransport transport) {
        synchronized (MUTEX) {
            this.transport = transport;
        }
    }

    public int getTransDelayMS() {
        return transDelayMS;
    }

    public void setTransDelayMS(int newTransDelayMS) {
        this.transDelayMS = newTransDelayMS;
    }


    private void assertExecutable() throws ModbusException {
        if (request == null || transport == null) {
            throw new ModbusException("Assertion failed, transaction not executable");
        }
    }

    @Override
    public void execute() throws ModbusException {
        //1. assert executeability
        assertExecutable();

        //3. write request, and read response,
        //   while holding the lock on the IO object
        int tries = 0;
        boolean finished = false;
        do {
            try {
                // Wait between adjacent requests
                ((ModbusSerialTransport) transport).waitBetweenFrames(transDelayMS, lastTransactionTimestamp);

                synchronized (MUTEX) {
                    //write request message
                    transport.writeRequest(request);
                    //read response message
                    response = transport.readResponse();
                    finished = true;
                }
            }
            catch (ModbusIOException e) {
                if (++tries >= retries) {
                    throw e;
                }
                ModbusUtil.sleep(getRandomSleepTime(tries));
                logger.debug("Execute try {} error: {}", tries, e.getMessage());
            }
        } while (!finished);

        //4. deal with exceptions
        if (response instanceof ExceptionResponse) {
            throw new ModbusSlaveException(((ExceptionResponse) response).getExceptionCode());
        }

        if (isCheckingValidity()) {
            checkValidity();
        }
        //toggle the id
        toggleTransactionID();

        // Set the last transaction timestamp
        lastTransactionTimestamp = System.currentTimeMillis();
    }

    private void checkValidity() throws ModbusException {

    }


    private void toggleTransactionID() {
        if (isCheckingValidity()) {
            if (transactionID == (Short.MAX_VALUE * 2)) {
                transactionID = 0;
            }
            else {
                transactionID++;
            }
        }
        request.setTransactionID(getTransactionID());
    }

}
